SQL注入之细节 - 入门篇

仁者见仁,智者见智

SQL注入漏洞产生的原因是网站应用程序在编写时未对用户提交至服务器的数据进行合法性校验,即没有进行有效地特殊字符过滤,导致网站服务器存在安全风险,这就是SQL Injection,即SQL注入漏洞。

本篇侧重介绍SQL注入预备知识与攻击手法的细节分析与总结
SQL注入文分为 细节篇 与 实战篇 两个篇幅互补来看可加深理解

预备知识

SQL 结构化查询语言

结构化查询语言(Structured Query Language) 简称 SQL

SQL学习

SQL基础语法简单易学,很快就能够掌握并使用

w3school

菜鸟教程

SQL注入基础知识

注入分类

如有补充可以在本文留言,如果感觉难以理解,也可以跳过,当你学习完SQL注入篇再回头看这一段。

基于从服务器接收到的响应

  • 基于错误的SQL注入;
  • 联合查询的类型;
  • 堆查询注射;
  • SQL盲注:
    • 基于布尔的SQL盲注;
    • 基于时间的SQL盲注;
    • 基于报错的SQL盲注;

基于如何处理输入的SQL查询(数据类型)

  • 字符串为基础的;
  • 数字或整数为基础的;

基于程序的顺序的注入(哪里发生了影响)

  • 一阶注入;
  • 二阶注入;

一阶注入 是指输入的注入语句对Web直接产生了影响,出现了结果; 二阶注入类似 存储型XSS 是指输入提交的语句,无法直接对Web应用程序产生影响,通过其他辅助间接的对Web产生危害,这样的注入就被称为 二阶注入

基于注入点的位置上的注入

  • 通过用户输入的表单域的注入;
  • 通过 cookie 注入;
  • 通过服务器变量注入;

系统函数

常用的几个函数

1
2
3
4
5
version()	# MySQL版本
user() # 数据库用户名
database() # 数据库名称
@@datadir # 数据库路径
@@version_compile_os # 操作系统版本

字符串连接函数

在select数据时,我们往往需要将数据进行连接后进行回显。很多的时候想将多个数据或者多行数据进行输出的时候,需要使用字符串连接函数。

CONCAT()函数

当我们不使用字符串连接函数时,查询语句返还结果是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> SELECT id,username FROM users;
+----+----------+
| id | username |
+----+----------+
| 1 | Dumb |
| 2 | Angelina |
| 3 | Dummy |
| 4 | secure |
| 5 | stupid |
| 6 | superman |
| 7 | batman |
| 8 | admin |
| 9 | admin1 |
| 10 | admin2 |
| 11 | admin3 |
| 12 | dhakkan |
| 14 | admin4 |
+----+----------+
13 rows in set (0.00 sec)

但是这里存在的一个问题是当使用union联合注入时,我们都知道,联合注入要求前后两个选择的列数要相同,这里id,name是两个列,当我们要一个列的时候,(当然不排除你先爆出id,再爆出name,分两次的做法)该怎么办?

CONCAT()语法及使用特点

CONCAT(str1,str2,…)

返回连接参数的字符串,可能有一个或多个参数。如有任何一个参数为NULL ,则返回值为 NULL。

CONCAT()使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
mysql> SELECT CONCAT('My', 'S', 'QL');
+-------------------------+
| CONCAT('My', 'S', 'QL') |
+-------------------------+
| MySQL |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT('My', NULL, 'QL');
+--------------------------+
| CONCAT('My', NULL, 'QL') |
+--------------------------+
| NULL |
+--------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT(14.3);
+--------------+
| CONCAT(14.3) |
+--------------+
| 14.3 |
+--------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT(14.3);
-> '14.3'

mysql> SELECT CONCAT(id, ',', username) FROM users;
+---------------------------+
| CONCAT(id, ',', username) |
+---------------------------+
| 1,Dumb |
| 2,Angelina |
| 3,Dummy |
| 4,secure |
| 5,stupid |
| 6,superman |
| 7,batman |
| 8,admin |
| 9,admin1 |
| 10,admin2 |
| 11,admin3 |
| 12,dhakkan |
| 14,admin4 |
+---------------------------+
13 rows in set (0.00 sec)

一般的我们都要用一个字符将各个项隔开,便于数据的查看。

CONCAT_WS()函数

CONCAT_WS 代表与分隔符连接,是CONCAT()的特殊形式。 第一个参数是其余参数的分隔符。 分隔符被添加在串联的字符串之间。 分隔符可以是一个字符串,其余的参数也是如此。 如果分隔符为NULL,则结果为NULL。CONCAT_WS()不会忽略任何空字符串, (然而会忽略所有的 NULL)。

CONCAT_WS()语法及其使用特点

CONCAT_WS(separator,str1,str2,…)

Separator为字符之间的分隔符

CONCAT_WS()使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> SELECT CONCAT_WS(',','First name','Second name','Last Name');
+-------------------------------------------------------+
| CONCAT_WS(',','First name','Second name','Last Name') |
+-------------------------------------------------------+
| First name,Second name,Last Name |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT_WS(',','First name',NULL,'Last Name');
+----------------------------------------------+
| CONCAT_WS(',','First name',NULL,'Last Name') |
+----------------------------------------------+
| First name,Last Name |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT_WS(NULL,'First name','Second name','Last Name');
+--------------------------------------------------------+
| CONCAT_WS(NULL,'First name','Second name','Last Name') |
+--------------------------------------------------------+
| NULL |
+--------------------------------------------------------+
1 row in set (0.00 sec)

GROUP_CONCAT() 函数

GROUP_CONCAT() 函数返回一个字符串结果,该结果由分组中的值连接组合而成。

使用语法及其特点

函数返回带有来自一个组的连接的非NULL值的字符串结果。该函数是一个增强的Sybase SQL Anywhere支持的基本LIST()函数。
GROUP_CONCAT([DISTINCT] expr [,expr …] [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name …]] [SEPARATOR str_val])

DISTINCT: 去除重复值;
expr [,expr …]: 一个或多个字段(或表达式)
ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name …]: 根据字段或表达式进行排序,可多个
SEPARATOR str_val: 分隔符(默认为英文逗号)

其实吧,这个讲的七七八八的,晕里晕乎的,我们还是边用边解释吧。

GROUP_CONCAT()使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- 我们来列出列出所有的数据库,先来看不使用GROUP_CONCAT()函数是个怎么样的效果
mysql> SELECT schema_name FROM information_schema.schemata;
+--------------------+
| schema_name |
+--------------------+
| information_schema |
| audit |
| challenges |
| dvwa |
| mysql |
| performance_schema |
| security |
| test |
| tptest |
| wangan |
+--------------------+
10 rows in set (0.00 sec) -- 返回的是个列表(list)里有多行(10 rows)

-- 我们再来看看使用了 GROUP_CONCAT() 函数是个什么效果
mysql> select group_concat(schema_name) from information_schema.schemata;
+-----------------------------------------------------------------------------------------------+
| group_concat(schema_name) |
+-----------------------------------------------------------------------------------------------+
| information_schema,audit,challenges,dvwa,mysql,performance_schema,security,test,tptest,wangan |
+-----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
--结果已经很明显了, GROUP_CONCAT()把多个字符串连接成了一个,默认使用逗号进行分割,所以我们可以看到使用了 GROUP_CONCAT()函数的查询语句,只回显一行 (1 rows)

mysql> select group_concat(schema_name SEPARATOR ';') from information_schema.schemata;
+-----------------------------------------------------------------------------------------------+
| group_concat(schema_name SEPARATOR ';') |
+-----------------------------------------------------------------------------------------------+
| information_schema;audit;challenges;dvwa;mysql;performance_schema;security;test;tptest;wangan |
+-----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) -- 使用分号分割

一般用于判断SQL注入的语句

PS : --+ 可以用 # 替换, url提交过程中url编码后的 #%23
or 1=1–+
‘ or 1=1–+
“ or 1=1–+
) or 1=1–+
‘) or 1=1–+
“) or 1=1–+
“)) or 1=1–+

存在SQL注入的代码可能像下面这样子:

1
2
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

可以产生一下联想,如果我们插入的 $id 多了一个引号会怎么样?

1
$sql="SELECT * FROM users WHERE id='1'' LIMIT 0,1";

因为多了一个引号,字符串无法闭合,会产生一条错误,所以此处考虑两个点,一个是闭合前面的 ' 另一个是把后面的 ' 处理掉(一般采用两种思路,闭合后面的引号或将他注释掉 -- # %23)

union 操作符的介绍

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同。

SQL UNION 语法

1
2
3
4
SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;
-- 注释:默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL。

SQL UNION ALL 语法

1
2
3
4
SELECT column_name(s) FROM table1
UNION ALL
SELECT column_name(s) FROM table2;
-- 注释:UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。

SQL 中的逻辑运算

提问

1
SELECT * FROM users WHERE id=1 and 1=1;

上面这条语句为什么能够选择出 id=1 的内容,and 1=1 到底起作用了没有? 这里就要清楚sql语句执行的顺序了。
这个问题我们在尝试万能密码的时候也会用到。

1
SELECT * FROM admin WHERE username='admin' AND password='' OR 1=1#'

' pr 1=1# 就是我们注入的语句。在上面这个语句执行后,我们在不知道密码的情况下就能登录admin用户了。原因其实在 WHERE 字句之后,我们可以看到多重判断的条件语句 username='admin' AND password='' or 1=1。我们用一个简单的方式来看待这条判断语句:

1
(username='admin') AND ((password='') OR (1=1));

在这里我使用了 () 规划出了他们的运算顺序emmm… 其实我表达能力有限,不知道怎么以最简洁的语言来描述这比较简单逻辑运算,相信有点编程基础或数学基础的,都能看的懂吧。

位运算

1
2
3
SELECT * FROM users WHERE id=1 and 1=1;
SELECT * FROM users WHERE id=1 && 1=1;
SELECT * FROM users WHERE id=1 & 1=1;

上面的语句中 第一行与第二行没有区别 &&AND 的另一种表达形式。

而第三行的意思是 id=1true 与 1进行位运算 结果还是1,再进行 = 操作( 1=1 ),结果还是1(ps: &优先级大于=)

此处进行的位运算。我们可以将数转换为二进制再进行与、或、非、异或等运算。必要的时候可以利用该方法进行诸如结果。例如将某一字符转换为ascii码后,可以分别与1,2,4,8,16,32等进行与运算,可以得到每一位的值,拼接起来就是ascii码值。再从ascii值反退回字符。

注入流程

判断注入类型 → 获取数据库名 → 获取数据库下的数据表名 → 获取当前数据表下的列名 → 获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
-- 查询数据库名
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| audit |
| challenges |
| dvwa |
| mysql |
| performance_schema |
| security |
| test |
| tptest |
| wangan |
+--------------------+
10 rows in set (0.00 sec)

-- 转换数据库为 security
mysql> use security;
Database changed

-- 查看数据库中存在的表名
mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails |
| referers |
| uagents |
| users |
+--------------------+
4 rows in set (0.00 sec)

-- 查看 emails 的表结构
mysql> desc emails;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| email_id | varchar(30) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
2 rows in set (0.07 sec)

-- 查看 users 的表结构
mysql> desc users;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | | NULL | |
| password | varchar(20) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

-- 待续

系统数据库 information_schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
-- 使用 information_schema 数据库
mysql> use information_schema;
Database changed

-- 查看表格
mysql> show tables;
+---------------------------------------+
| Tables_in_information_schema |
+---------------------------------------+
| CHARACTER_SETS |
| COLLATIONS |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLUMNS |
| COLUMN_PRIVILEGES |
| ENGINES |
| EVENTS |
| FILES |
| GLOBAL_STATUS |
| GLOBAL_VARIABLES |
| KEY_COLUMN_USAGE |
| PARAMETERS |
| PARTITIONS |
| PLUGINS |
| PROCESSLIST |
| PROFILING |
| REFERENTIAL_CONSTRAINTS |
| ROUTINES |
| SCHEMATA |
| SCHEMA_PRIVILEGES |
| SESSION_STATUS |
| SESSION_VARIABLES |
| STATISTICS |
| TABLES |
| TABLESPACES |
| TABLE_CONSTRAINTS |
| TABLE_PRIVILEGES |
| TRIGGERS |
| USER_PRIVILEGES |
| VIEWS |
| INNODB_BUFFER_PAGE |
| INNODB_TRX |
| INNODB_BUFFER_POOL_STATS |
| INNODB_LOCK_WAITS |
| INNODB_CMPMEM |
| INNODB_CMP |
| INNODB_LOCKS |
| INNODB_CMPMEM_RESET |
| INNODB_CMP_RESET |
| INNODB_BUFFER_PAGE_LRU |
+---------------------------------------+
40 rows in set (0.00 sec)

-- 我们先来枚举这张表
mysql> desc tables;
+-----------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------------------+------+-----+---------+-------+
| TABLE_CATALOG | varchar(512) | NO | | | |
| TABLE_SCHEMA | varchar(64) | NO | | | |
| TABLE_NAME | varchar(64) | NO | | | |
| TABLE_TYPE | varchar(64) | NO | | | |
| ENGINE | varchar(64) | YES | | NULL | |
| VERSION | bigint(21) unsigned | YES | | NULL | |
| ROW_FORMAT | varchar(10) | YES | | NULL | |
| TABLE_ROWS | bigint(21) unsigned | YES | | NULL | |
| AVG_ROW_LENGTH | bigint(21) unsigned | YES | | NULL | |
| DATA_LENGTH | bigint(21) unsigned | YES | | NULL | |
| MAX_DATA_LENGTH | bigint(21) unsigned | YES | | NULL | |
| INDEX_LENGTH | bigint(21) unsigned | YES | | NULL | |
| DATA_FREE | bigint(21) unsigned | YES | | NULL | |
| AUTO_INCREMENT | bigint(21) unsigned | YES | | NULL | |
| CREATE_TIME | datetime | YES | | NULL | |
| UPDATE_TIME | datetime | YES | | NULL | |
| CHECK_TIME | datetime | YES | | NULL | |
| TABLE_COLLATION | varchar(32) | YES | | NULL | |
| CHECKSUM | bigint(21) unsigned | YES | | NULL | |
| CREATE_OPTIONS | varchar(255) | YES | | NULL | |
| TABLE_COMMENT | varchar(2048) | NO | | | |
+-----------------+---------------------+------+-----+---------+-------+
21 rows in set (0.01 sec)

-- 使用如下查询 我们可以下载到表名。
mysql> select table_name from information_schema.tables where table_schema='security';
+------------+
| table_name |
+------------+
| emails |
| referers |
| uagents |
| users |
+------------+
4 rows in set (0.00 sec)

-- 结合一下之前学过的知识 使用 GROUP_CONCAT()函数
mysql> SELECT group_concat(table_name) AS 'table_name' FROM information_schema.tables WHERE table_schema='security';
+-------------------------------+
| table_name |
+-------------------------------+
| emails,referers,uagents,users |
+-------------------------------+
1 row in set (0.00 sec)

入门篇暂且到这里就结束了,细节篇暂且告一段落,万事开头难,对于新入门的小伙伴们,刚开始总是很迷茫的,面对着一箩筐一箩筐的知识,不知如何下手。往往学东西可能只掌握了表面,知其然而不知其所以然,

如果一件事情你不能讲清楚,十有八九你还没有完全理解。

参考来源

《MYSQL注入天书 - SQLi-labs使用手册》
http://www.cnblogs.com/lcamry